// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/

// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.

// The Original Code is MozShell Code.

// The Initial Developer of the Original Code is David L Dahl <ddahl@mozilla.com.
// Portions created by David L Dahl are Copyright (C) 2009
// All Rights Reserved.

// Contributor(s): Jesse Ruderman and Ted Mielczarek.

// a re-write of JS Shell, just for fun.
// Author: David Dahl <ddahl@mozilla.com>
//

// notes:
// The history functionality is taken from Jesse Ruderman's Javascript Shell
// The enumerateWindows function is taken from Ted Mielczarek's ShellCommands module.


var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;

EXPORTED_SYMBOLS = ["mozshell"];

var shell = {

  appName: 'mozShell',

  appVersion: '0.1alpha',

  appHome: 'http://code.google.com/p/moz-shell/',

  appNameVersion: function(){
    return "{n} {v}".supplant({n:this.appName,v:this.appVersion});
  },

  appAuthors: [{n:'David Dahl',e:'ddahl@mozilla.com'}],

  appDescription: function(){
    return '{appName} is a shell for Mozilla and Firefox extension developers, an attempt at making a JS Shell more "unix-shell-like"'.supplant({appName:this.appName});
  },

  help: function(){
    this.outputWrite(this.appNameVersion(),'title-1');
    //this.outputWrite("-----------------------------------------------------");
    var desc = this.appDescription();
    this.outputWrite(desc);
    //this.outputWrite("-----------------------------------------------------");
    for(var i=0;i < this.appAuthors.length;i++){
      this.outputWrite("{n} {e}".supplant(this.appAuthors[i]));
    }
    //this.outputWrite("-----------------------------------------------------");
    this.outputWrite("TODO: create 'help' text file to print out here.");
    //this.outputWrite("-----------------------------------------------------");
  },

  init: function(){
    // start it up
    this.enumerateWindows(true);
    this.input = document.getElementById("input");
    this.output = document.getElementById("output");
    // initialize scope to the same window as MozShell window
    this.scope = this.shellScope;
    this.windowList(false);
    this.keyCmds.setCaretPosition(0);
    this.makeSandbox(this.sandboxDefaultUri);
    var title = document.getElementById('mzsh-title');
    title.innerHTML = "{appName} {appVersion}".
                        supplant({appName:this.appName,
                                  appVersion:this.appVersion});
  },

  input: null,

  output: null,

  setScope: function(uri){
    // set the scope from the li element whose innerHTML
    // is the window to look up in 'windows'
    for (var i=0;i < this.windows.length;i++) {
      if (this.windows[i].uri === uri){
        this.scope = this.windows[i].win;
        var msg = document.createElement('div');
        msg.className = 'message';
        msg.innerHTML = 'mozshell.scope is set to the window object: {uri}'.
                          supplant({uri:this.windows[i].uri});
        this.output.appendChild(msg);
        //alert("Scope set to {s}".supplant({s:uri}));
        return;
      }
    }
  },

  sandboxDefaultUri: 'chrome://mozshell/content/mozshell.html',

  sandbox: null,

  sandboxEval: function(codeSnippet) {
    var res = Cu.evalInSandbox(codeSnippet, this.sandbox);
    return res;
  },

  makeSandbox: function(uri,name) {
    // initialize a sandbox
    if (uri) {
      this.sandboxUri = uri;
    } else {
      throw(this.exception('Sandbox error: a uri is required'));
    }
    if (name) {
      this.currentSandbox = name;
    }
    else {
      this.currentSandbox = uri;
    }
    this.sandbox = new Cu.Sandbox(this.sandboxUri);
    this.sandbox.mozshell = this;

  },

  sandboxUri: null,

  sandboxes: [],

  currentSandbox: 'mozshell',

  exception: function(msg){
    try {
      dump(msg);
    } catch(e) {
      //noop
    }
    alert(msg);
  },

  windowList: function (enumerate) {
    if (enumerate === true){
      this.enumerateWindows(false);
    }
    var div = document.createElement('div');
    var h1 = document.createElement('h1');
    h1.innerHTML = 'Discovered Windows:';
    div.appendChild(h1);
    var lst = document.createElement('ul');
    lst.className = 'window-list';
    if (this.windows.length > 0) {
      var li = document.createElement('li');
      li.className = 'error';
      li.innerHTML = "No Windows Found";
    }
    for (var i=0;i<this.windows.length; i++){
      var li = document.createElement('li');
      var lnk = document.createTextNode(this.windows[i].uri);
      //lnk.href = this.windows[i].uri;
      //lnk.innerHTML = this.windows[i].uri;
      li.onclick = 'window.mozshell.setScope(\'{uri}\');'
                     .supplant({uri:this.windows[i].uri});
      li.appendChild(lnk);
      lst.appendChild(li);
    }
    div.appendChild(lst);
    this.output.appendChild(div);
  },

  enumerateWindows: function (setScope) {
    // originally part of Ted's shellCommands obj in Ex. Dev. Ex.
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
               getService(Components.interfaces.nsIWindowMediator);
    var en = wm.getEnumerator("");
    var n = 0;
    this.windows = [];
    while(en.hasMoreElements()) {
      var w = en.getNext();
      if(w.document.getElementById("content") &&
         w.document.getElementById("content").tagName == "tabbrowser") {
         var b = w.document.getElementById("content");
         var ntabs = b.mPanelContainer.childNodes.length;
         for(var i=0; i<ntabs; i++) {
           var tb = b.getBrowserAtIndex(i);
           try {
	         this.windows[n] = {win:tb.contentWindow};
             this.windows[n].uri = tb.currentURI.spec;
             if (setScope === true){
               if (this.windows[n].uri ===
                 'chrome://mozshell/content/mozshell.html'){
                 this.shellScope = this.windows[n].win;
               }
             }
             n++;
           }
           catch(e) {
             dump(e);
           }
         }
      }
      else if(w.location.href) {
        this.windows[n] = {win:w,uri:w.location.href};
        if (setScope === true){
          if (this.windows[n].uri ===
            'chrome://mozshell/content/mozshell.html'){
            this.shellScope = this.windows[n].win;
          }
        }
        n++;
      }
    }
  },

  chooseScope: function (){
    // allow user to choose scope to work with via enumerateWindows

  },

  windows: [],

  shellScope: {},

  scope: {},

  initContext: function(){

  },

  launch: function(){
    // open window
    var w = window.openDialog(
                      "chrome://mozshell/content/mozshell.html",
                      "_blank",
                      "all=no,width=800,height=600,scrollbars=yes,resizable=yes,dialog=no");
  },

  refocus: function(){
    try{
      this.input.blur();
      this.input.focus();
    }
    catch(e) {
      dump(e);
    }
  },

  keepFocusInTextbox: function (e) {
    var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard
    while (!g.tagName){
      g = g.parentNode;
    }

    var t = g.tagName.toUpperCase();

    if (t=="A" || t=="INPUT") {
      return;
    }

    if (window.getSelection) {
      // Mozilla
      if (String(window.getSelection()))
        return;
    }
    else if (document.getSelection) {
      // Opera? Netscape 4?
      if (document.getSelection()) {
        return;
      }
    }
    else {
      // IE
      if ( document.selection.createRange().text) {
        return;
      }
    }
    this.refocus();
  },

  history: ['mozshell.windowList()'],

  historyIdx: 1,

  historyAdd: function(cmd){
    this.history[this.history.length-1] = cmd;
    this.history[this.history.length] = "";
    this.historyIdx = this.history.length - 1;
  },

  historyPeruse: function (up) {
    // go back through the history via up arrow
    // stolen from javascript shell 1.4
    var L = this.history.length;

    if (L == 1){
      return;
    }

    if (up) {
      if (this.historyIdx == L-1) {
        // Save this entry in case the user hits the down key.
        this.history[this.historyIdx] = this.input.value;
      }
      if (this.historyIdx > 0) {
        this.historyIdx--;
        // Use a timeout to prevent up from moving cursor within new text
        // Set to nothing first for the same reason
        setTimeout(
          function() {
            window.mozshell.input.value = '';
            window.mozshell.input.value =
                window.mozshell.history[window.mozshell.historyIdx];
            var caretPos = window.mozshell.input.value.length;
            if (window.mozshell.input.setSelectionRange) {
              window.mozshell.input.setSelectionRange(caretPos, caretPos);
            }
          },0);
      }
    }
    else {
      if (this.historyIdx < L-1){
        this.historyIdx++;
        this.input.value = this.history[this.historyIdx];
      }
      else if (this.historyIdx == L-1) {
        // Already on the current entry: clear but save
        if (this.input.value) {
          this.history[this.historyIdx] = this.input.value;
          ++this.historyIdx;
          this.input.value = "";
        }
      }
    }
  },

  lastCmd: null,

  win: null,

  cmdIn: null,

  output: null,

  errorStack: [],

  lastError: null,

  middleware: {},

  execute: function mzExecute(){
    var _script = this.input.value;
    this.lastCmd = _script;
    this.historyAdd(_script);
    this.echoInput();
    with(this.scope){
      with(this.sandbox){
        // default is the same window as MozShell window.
        try {
          //this.echoOutput(this.sandboxEval(_script));
          this.echoOutput(window.eval(_script));
        }
        catch(e) {
          dump(e);
          this.outputWrite(e,'error');
        }
      }
    }
  },

  filterProps: function mzFilterProps(obj, filter, objName) {
    // filter results of propeties in an object and
    // spit it out to the console
    // XXX: add paging via a Generator
    var result = "";
    if (filter) {
      // create regex filter
      var reg = new RegExp(filter, "i");
    }
    if(!objName){
      objName = String(obj);
    }
    for (var i in obj) {
      if (filter){
        if (reg.test(obj[i])){
          result += objName + "['" + i + "'] = " + obj[i] + "\n";
        }
      } else {
        result += objName + "['" + i + "'] = " + obj[i] + "\n";
      }
    }
      this.outputWrite(result,'preserveFormat');
  },


  props : function props(e, onePerLine) {
    // fixme: adjust to mozshell output.
    if (e === null) {
      println("props called with null argument", "error");
      return;
    }

    if (e === undefined) {
      println("props called with undefined argument", "error");
      return;
    }

    var ns = ["Methods", "Fields", "Unreachables"];
    var as = [[], [], []]; // array of (empty) arrays of arrays!
    var p, j, i; // loop variables, several used multiple times

    var protoLevels = 0;

    for (p = e; p; p = p.__proto__) {
      for (i=0; i<ns.length; ++i)
        as[i][protoLevels] = [];
      ++protoLevels;
    }

    for(var a in e) {
      // Shortcoming: doesn't check that VALUES are the same in object and prototype.
      var protoLevel = -1;
      try {
        for (p = e; p && (a in p); p = p.__proto__)
          ++protoLevel;
      }
      catch(er) {
        protoLevel = 0;
      } // "in" operator throws when param to props() is a string

      var type = 1;
      try {
        if ((typeof e[a]) == "function")
          type = 0;
      }
      catch (er) { type = 2; }

      as[type][protoLevel].push(a);
    }

    function times(s, n) { return n ? s + times(s, n-1) : ""; }

    for (j=0; j<protoLevels; ++j)
      for (i=0;i<ns.length;++i)
        if (as[i][j].length)
          printWithRunin(
            ns[i] + times(" of prototype", j),
            (onePerLine ? "\n\n" : "") + as[i][j].sort().join(onePerLine ? "\n" : ", ") + (onePerLine ? "\n\n" : ""),
            "propList"
          );
  },

  propsOld: function(obj){
    // display all properies of an object
    for (var p in obj){
      var t = typeof(p);
      // if obj, recurse:
      var _o = "{p}:{t}".supplant({p:p,t:t});
      this.echoOutput(_o);
    }
  },

  currentInput: null,

  echoInput: function(){
    // display what was entered into the terminal
    if(this.input.value === "") {
      return false;
    } else {
      this.currentInput = this.input.value;
      this.input.value = '';
      var _out = document.createElement("div");
      _out.innerHTML = this.currentInput;
      _out.className = 'normalOutput';
      this.output.appendChild(_out);
      return true;
    }
  },

  echoOutput: function(evalD){
    // display results of eval'd input
    if (evalD !== undefined) {
      if(typeof(evalD) == 'function'){
        this.outputWrite(evalD, "preserveFormat");
      } else{
        this.outputWrite(evalD, "normalOutput");
      }
    }
  },

  outputWrite: function(outputData,klass){
    // write output to the output div.
    // make sure to add the correct className to each result div
    // based on what kind of output is being written
    var _out = document.createElement("div");
    if ((outputData=String(outputData))){
      if(klass){
        // we know how to style the data - usually normal output
        _out.className = klass;
        if (klass === 'preserveFormat'){
          var prettyCode = prettyPrintOne(outputData);
          _out.innerHTML = prettyCode;
        }
        else {
          _out.innerHTML = outputData;
        }
      } else {
        _out.className = 'undeterminedOutput';
      }
      this.output.appendChild(_out);
    } else{
      // output data is not a string?
      //var opd = typeof(outputData);
      //_out.innerHTML = "Output data was not string, was : {opd}".
      //                   supplant({opd:opd});
      _out.className = 'message';
      _out.innerHTML = outputData;
      this.output.appendChild(_out);
    }
  },

  keydown: function(e){
    if (e.ctrlKey && e.keyCode == 65){
      // control-a
      // set cursor to beginning of textarea
      setTimeout(function(){window.mozshell.keyCmds.ctrlA();},0);
    }
    else if (e.ctrlKey && e.keyCode == 69) {
      // control-e
      setTimeout(function(){window.mozshell.keyCmds.ctrlE();},0);
    }
    else if (e.shiftKey && e.keyCode == 13) {
      // shift-enter
      // don't do anything; allow the shift-enter to insert
      // a line break as normal
    }
    else if (e.keyCode == 13) {
      // enter
      // execute the input on enter
      this.execute();
      //this.executeViaWin();
      setTimeout(function() { window.mozshell.input.value = ""; }, 0);
      // can't preventDefault on input, so clear it later
    }
    else if (e.keyCode == 38) {
      // up
      // go up in history if at top or ctrl-up
      if (e.ctrlKey || this.caretInFirstLine()){
        this.historyPeruse(true);
      }
    }
    else if (e.keyCode == 40) {
      // down
      // go down in history if at end or ctrl-down
      if (e.ctrlKey || this.caretInLastLine()){
        this.historyPeruse(false);
      }
    }
    else if (e.keyCode == 9) {
      // tab
      this.tabComplete();
      setTimeout(function() { window.mozshell.refocus(); }, 0);
      // refocus because tab was hit
    }
    setTimeout(window.mozshell.recalculateInputHeight, 0);
  },

  keyCmds: {

    setCaretPosition: function (caretPos) {
      var elem = window.mozshell.input;
      if(elem != null) {
        if(elem.createTextRange) {
          var range = elem.createTextRange();
          range.move('character', caretPos);
          range.select();
        }
        else if (elem.selectionStart) {
          elem.focus();
          elem.setSelectionRange(caretPos, caretPos);
        }
        else {
          elem.focus();
        }
      }
    },

    ctrlA: function(){
      var tmp =   window.mozshell.input.value;
      window.mozshell.input.value = '';
      setTimeout(function(){window.mozshell.input.value = tmp; window.mozshell.keyCmds.setCaretPosition(0);},50);
    },

    ctrlE: function(){
      var tmp =   window.mozshell.input.value;
      window.mozshell.input.value = '';
      setTimeout(function(){
        window.mozshell.input.value = tmp;
        var endPos = tmp.length + 1;
        window.mozshell.keyCmds.setCaretPosition(endPos);
      },50);
    }

  },

  caretInFirstLine: function(){
    // IE doesn't support selectionStart/selectionEnd
    if (this.input.selectionStart == undefined){
      return true;
    }
    var firstLineBreak = this.input.value.indexOf("\n");
    return ((firstLineBreak == -1) || (this.input.selectionStart <= firstLineBreak));
  },

  caretInLastLine: function () {
    // IE doesn't support selectionStart/selectionEnd
    if (this.input.selectionEnd == undefined){
      return true;
    }
    var lastLineBreak = this.input.value.lastIndexOf("\n");
    return (this.input.selectionEnd > lastLineBreak);
  },

  recalculateInputHeight: function () {
    try {
      var rows = window.mozshell.input.value.split(/\n/).length + 1;
    } catch(e) {
      dump(e);
    }
  },

  tabComplete: function (){
    tabcomplete();
  },

  jsBrCheck: function(){
    // check if we can run jsbridge commands
  },

  jsBrRun: function(command,args){
    // send a command to jsbridge with args [...]
  }
};

var jsBrCmds = {
  // jsbridge command templates
  grepSource: function(str, path){
    // grep through the mozilla source code for string recursively from
    // path
  }
};


function loadMiddleware(obj,middleware){
  if (middleware.in && middleware.out && middleware.error){
    // looks like the interface is implemented: load it
    obj.middleware[middleware.name] = middleware;
  } else {
    throw(shellError());
  }

};


// tab complete - this is stolen wholesale from Javascript Shell 1.4
// needs a re-write
var tooManyMatches = null;

function tabcomplete()
{
  /*
   * Working backwards from s[from], find the spot
   * where this expression starts.  It will scan
   * until it hits a mismatched ( or a space,
   * but it skips over quoted strings.
   * If stopAtDot is true, stop at a '.'
   */
  function findbeginning(s, from, stopAtDot)
  {
    /*
     *  Complicated function.
     *
     *  Return true if s[i] == q BUT ONLY IF
     *  s[i-1] is not a backslash.
     */
    function equalButNotEscaped(s,i,q)
    {
      if(s.charAt(i) != q) // not equal go no further
        return false;

      if(i==0) // beginning of string
        return true;

      if(s.charAt(i-1) == '\\') // escaped?
        return false;

      return true;
    }

    var nparens = 0;
    var i;
    for(i=from; i>=0; i--)
    {
      if(s.charAt(i) == ' ')
        break;

      if(stopAtDot && s.charAt(i) == '.')
        break;

      if(s.charAt(i) == ')')
        nparens++;
      else if(s.charAt(i) == '(')
        nparens--;

      if(nparens < 0)
        break;

      // skip quoted strings
      if(s.charAt(i) == '\'' || s.charAt(i) == '\"')
      {
        //dump("skipping quoted chars: ");
        var quot = s.charAt(i);
        i--;
        while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
          //dump(s.charAt(i));
          i--;
        }
        //dump("\n");
      }
    }
    return i;
  }

  // XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code)
  // XXX doesn't work in IE, even though it contains IE-specific code
  function getcaretpos(inp)
  {
    if(inp.selectionEnd != null)
      return inp.selectionEnd;

    if(inp.createTextRange)
    {
      var docrange = window.mozshell.scope.document.selection.createRange();
      var inprange = inp.createTextRange();
      if (inprange.setEndPoint)
      {
        inprange.setEndPoint('EndToStart', docrange);
        return inprange.text.length;
      }
    }

    return inp.value.length; // sucks, punt
  }

  function setselectionto(inp,pos)
  {
    if(inp.selectionStart) {
      inp.selectionStart = inp.selectionEnd = pos;
    }
    else if(inp.createTextRange) {
      var docrange = window.mozshell.scope.document.selection.createRange();
      var inprange = inp.createTextRange();
      inprange.move('character',pos);
      inprange.select();
    }
    else { // err...
    /*
      inp.select();
      if(_win.Shell.document.getSelection())
        _win.Shell.document.getSelection() = "";
        */
    }
  }
    // get position of cursor within the input box
    var caret = getcaretpos(window.mozshell.input);

    if(caret) {
      //dump("----\n");
      var dotpos, spacepos, complete, obj;
      //dump("caret pos: " + caret + "\n");
      // see if there's a dot before here
      dotpos = findbeginning(window.mozshell.input.value, caret-1, true);
      //dump("dot pos: " + dotpos + "\n");
      if(dotpos == -1 || window.mozshell.input.value.charAt(dotpos) != '.') {
        dotpos = caret;
//dump("changed dot pos: " + dotpos + "\n");
      }

      // look backwards for a non-variable-name character
      spacepos = findbeginning(window.mozshell.input.value, dotpos-1, false);
      //dump("space pos: " + spacepos + "\n");
      // get the object we're trying to complete on
      if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret)
      {
        // try completing function args
        if(window.mozshell.input.value.charAt(dotpos) == '(' ||
 (window.mozshell.input.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))
        {
          var fn,fname;
  var from = (window.mozshell.input.value.charAt(dotpos) == '(') ? dotpos : spacepos;
          spacepos = findbeginning(window.mozshell.input.value, from-1, false);

          fname = window.mozshell.input.value.substr(spacepos+1,from-(spacepos+1));
  //dump("fname: " + fname + "\n");
          try {
            with(window.mozshell.scope){
              fn = eval(fname);
            }
          }
          catch(er) {
            //dump('fn is not a valid object\n');
            return;
          }
          if(fn == undefined) {
             //dump('fn is undefined');
             return;
          }
          if(fn instanceof Function)
          {
            // Print function definition, including argument names, but not function body
            if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/))
              window.mozshell.outputWrite(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete");
          }

          return;
        }
        else
          obj = window.mozshell.scope;
      }
      else
      {
        var objname = window.mozshell.input.value.substr(spacepos+1,dotpos-(spacepos+1));
        //dump("objname: |" + objname + "|\n");
        try {
          with(window.mozshell.scope){
            obj = eval(objname);
          }

        }
        catch(er) {
          printError(er);
          return;
        }
        if(obj == undefined) {
          // sometimes this is tabcomplete's fault, so don't print it :(
          // e.g. completing from "print(document.getElements"
          // window.mozshell.outputWrite("Can't complete from null or undefined expression " + objname, "error");
          return;
        }
      }
      //dump("obj: " + obj + "\n");
      // get the thing we're trying to complete
      if(dotpos == caret)
      {
        if(spacepos+1 == dotpos || spacepos == dotpos)
        {
          // nothing to complete
          //dump("nothing to complete\n");
          return;
        }

        complete = window.mozshell.input.value.substr(spacepos+1,dotpos-(spacepos+1));
      }
      else {
        complete = window.mozshell.input.value.substr(dotpos+1,caret-(dotpos+1));
      }
      //dump("complete: " + complete + "\n");
      // ok, now look at all the props/methods of this obj
      // and find ones starting with 'complete'
      var matches = [];
      var bestmatch = null;
      for(var a in obj)
      {
        //a = a.toString();
        //XXX: making it lowercase could help some cases,
        // but screws up my general logic.
        if(a.substr(0,complete.length) == complete) {
          matches.push(a);
          ////dump("match: " + a + "\n");
          // if no best match, this is the best match
          if(bestmatch == null)
          {
            bestmatch = a;
          }
          else {
            // the best match is the longest common string
            function min(a,b){ return ((a<b)?a:b); }
            var i;
            for(i=0; i< min(bestmatch.length, a.length); i++)
            {
              if(bestmatch.charAt(i) != a.charAt(i))
                break;
            }
            bestmatch = bestmatch.substr(0,i);
            ////dump("bestmatch len: " + i + "\n");
          }
          ////dump("bestmatch: " + bestmatch + "\n");
        }
      }
      bestmatch = (bestmatch || "");
      ////dump("matches: " + matches + "\n");

      var objAndComplete = (objname || obj) + "." + bestmatch;
      //dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n");
      if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) {

        window.mozshell.outputWrite("Matches: " + matches.join(', '), "tabcomplete");
        tooManyMatches = null;
      }
      else if(matches.length > 20)
      {
        window.mozshell.outputWrite(matches.length + " matches.  Press tab again to see them all", "tabcomplete");
        tooManyMatches = objAndComplete;
      }
      else {
        tooManyMatches = null;
      }
      if(bestmatch != "")
      {
        var sstart;
        if(dotpos == caret) {
          sstart = spacepos+1;
        }
        else {
          sstart = dotpos+1;
        }
        window.mozshell.input.value = window.mozshell.input.value.substr(0, sstart)
                  + bestmatch
                  + window.mozshell.input.value.substr(caret);
        setselectionto(window.mozshell.input,caret + (bestmatch.length - complete.length));
      }
    }
}

